package view;

import controller.Controller;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ResourceBundle;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import model.Table;


/**
 * GUI elements
 * 
 * @author Rex
 */
public class Displayer extends JFrame {

    private static final String INVALID_INPUT_ERROR =
        "Please select file, mode, and ALL categories correctly...";
    private static final String MODE_UNSUPPORTED = "This mode is not possible";
    private static final String FAILURE_TO_CREATE =
        "Failed to create a mode instance although this mode is supported";

    private Controller myController;
    private JFileChooser myChooser;

    // swing components
    private GeneralDisplayMode myDisplayArea;
    private Table myTable;
    private JTextArea myTextArea;
    private JComponent myDisplayScrollPane;
    private JComponent myButtonPanel;

    // get strings from resource file
    private ResourceBundle myResources;
    // String that represents the class name of the current mode
    private String myModeClass;

    private String[] myCategories;
    // categories selected by user
    private String[] mySelectedCategories;

    /**
     * Constructor
     * 
     * @param title Title of the graph
     * @param language Language used for menues and buttons
     * @param c Controller instance
     * @param categories String of Categories
     */
    public Displayer (String title, String language, Controller c,
            String[] categories) {
        // set properties of frame
        setTitle(title);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        myChooser = new JFileChooser(System.getProperties().getProperty(
                "user.dir"));
        myController = c;
        myCategories = categories;
        // default: display null mode
        myModeClass = "NullMode";

        // create and arrange sub-parts of the GUI
        myResources = ResourceBundle.getBundle("resources." + language);
        myDisplayArea = new NullMode();
        myTextArea = new JTextArea("info");
        myDisplayScrollPane = myDisplayArea.makeGraph();
        myButtonPanel = new JPanel();
        makeButtons(myButtonPanel);
        getContentPane().add(myButtonPanel, BorderLayout.NORTH);
        getContentPane().add(myDisplayScrollPane, BorderLayout.CENTER);
        getContentPane().add(myTextArea, BorderLayout.SOUTH);
        makeMenus();

        // size and display the GUI
        pack();
        setVisible(true);
        setAlwaysOnTop(false);
        setResizable(true);
    }

    /**
     * make buttons on the panel (currently it has two buttons)
     * 
     * @param buttonPanel
     */
    private void makeButtons (JComponent buttonPanel) {
        buttonPanel.add(makeRefreshButton());
        buttonPanel.add(makeClearButton());
    }

    /**
     * create drop down list that enables user to choose categories to be
     * displayed
     * 
     * @param index mySelectedCategories[index] to store the selected String
     * @return a drop down list
     */
    @SuppressWarnings("rawtypes")
    private JComboBox makeDropDownCategories (final int index) {
        myCategories[0] = "Please select!";
        @SuppressWarnings("unchecked")
        JComboBox result = new JComboBox(myCategories);
        result.addItemListener(new ItemListener() {
            public void itemStateChanged (ItemEvent event) {
                if (event.getStateChange() == ItemEvent.SELECTED) {
                    mySelectedCategories[index] = event.getItem().toString();
                }
            }
        });
        return result;
    }

    /**
     * create the "Generate" Button that generates the graph for a new set of
     * data
     * 
     * @return "Generate" Button
     */
    private JButton makeRefreshButton () {
        JButton result = new JButton(myResources.getString("RefreshCommand"));
        result.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed (ActionEvent e) {
                // Fetch Table according to the Categories
                if (categoriesSelected()) {
                    myTable = myController.getSubTable(mySelectedCategories);
                }
                else {
                    showError(INVALID_INPUT_ERROR);
                    return;
                }
                @SuppressWarnings("rawtypes")
                // constructor for the particular mode class
                Constructor modeConstructor = null;
                try {
                    modeConstructor = Class.forName("view." + myModeClass).getConstructor(
                            Table.class, String[].class);
                }
                catch (NoSuchMethodException e1) {
                    showError(MODE_UNSUPPORTED);
                    e1.printStackTrace();
                }
                catch (ClassNotFoundException e1) {  
                    System.err.println(myModeClass + " class does not exist!");
                }
                catch (SecurityException e1) {
                    System.err.println("Failed because of Security Issue!");
                    e1.printStackTrace();
                }

                // construct the mode class selected by the user
                try {
                    myDisplayArea = (GeneralDisplayMode) (modeConstructor.newInstance(
                            myTable, mySelectedCategories));
                }
                catch (InstantiationException e1) {
                    System.err.println(FAILURE_TO_CREATE);
                }
                catch (IllegalAccessException e1) {  
                    System.err.println("Constructor should be made public!");
                }
                catch (IllegalArgumentException e1) {  
                    System.err.println("Table / categories are not valid!");
                    e1.printStackTrace();
                }
                catch (InvocationTargetException e1) {  
                    e1.printStackTrace();
                }
                drawGraph();
            }
        });
        return result;
    }

    private void drawGraph () {
        remove(myDisplayScrollPane);
        myDisplayScrollPane = new JScrollPane();
        myDisplayScrollPane = new JScrollPane(myDisplayArea.makeGraph());
        add(myDisplayScrollPane, BorderLayout.CENTER);
        refreshScreen();
    }

    /**
     * category selected
     * 
     * @return if ALL the categories are selected
     */
    private boolean categoriesSelected () {
        if (mySelectedCategories == null) { return false; }
        for (String s : mySelectedCategories) {
            if (s.equals(myCategories[0])) { return false; }
        }
        return true;
    }

    /**
     * make the clear button
     * 
     * @return a button when clicked, delete all the data displayed from screen
     */
    private JButton makeClearButton () {
        JButton result = new JButton(myResources.getString("ClearCommand"));
        result.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed (ActionEvent e) {
                clearScreen();
            }
        });
        return result;
    }

    /**
     * clear the screen, including buttons, but not menus
     */
    private void clearScreen () {
        mySelectedCategories = null;
        myButtonPanel.removeAll();
        makeButtons(myButtonPanel);

        myDisplayArea = new NullMode();
        drawGraph();
    }

    /**
     * refresh the screen to display updated information
     */
    private void refreshScreen () {
        Dimension mySize = getSize();
        setSize(new Dimension(mySize.width, mySize.height - 1));
        setSize(mySize);
        myTextArea.setText("Current Mode: " + myModeClass);
        pack();
    }

    /**
     * make menus
     */
    protected void makeMenus () {
        JMenuBar bar = new JMenuBar();
        bar.add(makeFileMenu());
        bar.add(makeModeMenu());
        bar.add(makeSortMenu(this));
        setJMenuBar(bar);
    }

    /**
     * sort in different orders
     * 
     * @param myFrame the Displayer
     * @return sort menu
     */
    protected JMenu makeSortMenu (final JFrame myFrame) {
        JMenu result = new JMenu(myResources.getString("SortMenu"));
        result.add(new AbstractAction(myResources.getString("SortAscending")) {
            @Override
            public void actionPerformed (ActionEvent e) {
                myDisplayArea.sortAscending();
                drawGraph();
            }
        });
        result.add(new AbstractAction(myResources.getString("SortDescending")) {
            @Override
            public void actionPerformed (ActionEvent e) {
                myDisplayArea.sortDescending();
                drawGraph();
            }
        });
        return result;
    }

    /**
     * make the mode menu
     * 
     * @return mode menu that allows people to choose display modes
     */
    protected JMenu makeModeMenu () {
        JMenu myModeMenu = new JMenu(myResources.getString("ModeMenu"));
        class ChangeModeAction extends AbstractAction {
            private int myNumberOfVariables;
            private String myMode;

            public ChangeModeAction (String mode, int numberOfVariables) {
                super(myResources.getString(mode));
                myNumberOfVariables = numberOfVariables;
                myMode = mode;
            }

            public void actionPerformed (ActionEvent e) {
                // change the amount of drop down lists needed;
                myModeClass = myMode;
                myButtonPanel.removeAll();
                makeButtons(myButtonPanel);
                mySelectedCategories = new String[myNumberOfVariables];
                for (int i = 0; i < myNumberOfVariables; i++) {
                    myButtonPanel.add(makeDropDownCategories(i));
                    // initialize mySelectedCategories
                    mySelectedCategories[i] = myCategories[0];
                }
                myModeClass = myMode;
                refreshScreen();
            }
        }
        myModeMenu.add(new ChangeModeAction("BarChartMode", 1));
        myModeMenu.add(new ChangeModeAction("ScatterPlotMode", 2));
        return myModeMenu;
    }

    /**
     * file menu
     * 
     * @return file menu that allows user to choose file or quit program
     */
    protected JMenu makeFileMenu () {
        JMenu result = new JMenu(myResources.getString("FileMenu"));
        result.add(new AbstractAction(myResources.getString("OpenCommand")) {
            @Override
            public void actionPerformed (ActionEvent e) {

                int response = myChooser.showOpenDialog(null);
                if (response == JFileChooser.APPROVE_OPTION) {
                    if (myController.parse(myChooser.getSelectedFile())) {
                        clearScreen();
                    }
                    myCategories = myController.getCategories();
                    refreshScreen();
                }
            }
        });

        result.add(new JSeparator());
        result.add(new AbstractAction(myResources.getString("QuitCommand")) {
            @Override
            public void actionPerformed (ActionEvent e) {
                System.exit(0);
            }
        });
        return result;
    }

    /**
     * display error in a pop-up window
     * 
     * @param message error message
     */
    public void showError (String message) {
        JOptionPane.showMessageDialog(this, message, "Input Error",
                JOptionPane.ERROR_MESSAGE);
    }
}
